//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2004-2007
//
// GPX files parser.
//

# include "StdAfx.h"
# include "Map.h"
# include "Load.h"
# include "Types.h"
# include "ParseHelpers.h"
# include "Log.h"
# include "Status.h"
# include "Wrappers.h"
# include "fast_floor.h"
# include "Globals.h"

class CGpxLoader : public CMapLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);

  private:
	map_t * m_pMap;

	file_refs_t m_file_refs;

	CComBSTR m_bstrLat;
	CComBSTR m_bstrLon;
	void GetPosition (IXMLDOMNode * _pNode, point_t & _point);
};

DECLARE_WPTRK_LOADER (CGpxLoader, "topografix-gpx", "gpx", "GPS eXchange format (*.gpx)", ftiWTR | ftiDontMapFile);

bool CGpxLoader::IsValid (const char * _pData, size_t _cFileSize) const {
	return
		HasPrefix (_pData, _pData + _cFileSize, "<?xml", 5) ||                              // ASCII
		HasPrefix (_pData, _pData + _cFileSize, "\xEF\xBB\xBF<?xml", 8) ||                  // UTF-8
		(_cFileSize > 12 && ::memcmp (_pData, "\xFF\xFE<\0?\0x\0m\0l\0", _cFileSize) == 0); // UTF-16
}

static
bool _ParseDateTime (const wchar_t * _str, const wchar_t * _strEnd, DATE & _dt, bool & _bHasMilliseconds) {
	// ISO 8601 'dateTime' type
	// Sample: 2001-11-28T21:05:28Z

	const wchar_t * p = _str;

	// Year.
	unsigned long ulYear = 0;
	if (! ParseDecU (p, _strEnd, & p, ulYear))
		return false;

	if (* p != L'-')
		return false;
	++ p;

	// Month.
	unsigned long ulMonth = 0;
	if (! ParseDecU (p, _strEnd, & p, ulMonth))
		return false;

	if (* p != L'-')
		return false;
	++ p;

	// Day.
	unsigned long ulDay = 0;
	if (! ParseDecU (p, _strEnd, & p, ulDay))
		return false;

	if (* p != L'T')
		return false;
	++ p;

	// Hour.
	unsigned long ulHour = 0;
	if (! ParseDecU (p, _strEnd, & p, ulHour))
		return false;

	if (* p != L':')
		return false;
	++ p;

	// Minute.
	unsigned long ulMinute = 0;
	if (! ParseDecU (p, _strEnd, & p, ulMinute))
		return false;

	if (* p != L':')
		return false;
	++ p;

	// Seconds and milliseconds.
	float fSecond = 0;
	if (! ParseReal (p, _strEnd, & p, fSecond))
		return false;
	const int nSecond = fast_floor (fSecond);
	const int nMS = fast_floor ((fSecond - nSecond)*1000);

	if (* p != L'Z')
		return false;

	SYSTEMTIME st = {ulYear, ulMonth, 0, ulDay, ulHour, ulMinute, nSecond, 0};
	if (! ::SystemTimeToVariantTime (& st, & _dt))
		return false;

	if (nMS) {
		_dt += nMS/24./60./60./1000;
		_bHasMilliseconds = true;
	} else
		_bHasMilliseconds = false;

	return true;
}

# include "msxml.h"

void CGpxLoader::GetPosition (IXMLDOMNode * _pNode, point_t & _point) {
	CComPtr<IXMLDOMNamedNodeMap> pNamedNodeMap;
	_pNode->get_attributes (& pNamedNodeMap);

	// Latitude.
	CComPtr<IXMLDOMNode> pLatNode;
	pNamedNodeMap->getNamedItem (m_bstrLat, & pLatNode);
	CComVariant varLat;
	pLatNode->get_nodeValue (& varLat);
	varLat.ChangeType (VT_BSTR);
	_point.y = ParseReal (varLat.bstrVal, varLat.bstrVal + ::SysStringLen (varLat.bstrVal));

	// Longitude.
	CComPtr<IXMLDOMNode> pLonNode;
	pNamedNodeMap->getNamedItem (m_bstrLon, & pLonNode);
	CComVariant varLon;
	pLonNode->get_nodeValue (& varLon);
	varLon.ChangeType (VT_BSTR);
	_point.x = ParseReal (varLon.bstrVal, varLon.bstrVal + ::SysStringLen (varLon.bstrVal));
}

LoadStatus_t CGpxLoader::Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap) {
	//
	// Prepare MS XML.
	//

	CCom com;

	USES_CONVERSION;

	CComPtr<IXMLDOMDocument> pDocument;
	if (FAILED (pDocument.CoCreateInstance (__uuidof (DOMDocument)))) {
		ReportWarning ("Microsoft XML is not available at this computer. Please install MS XML.");
		return lsFailed;
	}

	pDocument->put_async (VARIANT_FALSE);

	VARIANT_BOOL vbSuccess = VARIANT_FALSE;
	if (FAILED (pDocument->load (CComVariant (_strFileName), & vbSuccess)) || vbSuccess == VARIANT_FALSE) {
		ReportWarning ("XML loading has failed.");
		return lsFailed;
	}

	CComPtr<IXMLDOMNodeList> pGpxNodeList;
	if (FAILED (pDocument->getElementsByTagName (CComBSTR (L"gpx"), & pGpxNodeList)) || pGpxNodeList == NULL) {
		ReportWarning ("XML file does not contain <GPX> node.");
		return lsFailed;
	}

	CComPtr<IXMLDOMNodeList> pWptNodeList;
	pDocument->getElementsByTagName (CComBSTR (L"gpx/wpt"), & pWptNodeList);

	CComPtr<IXMLDOMNodeList> pTrkNodeList;
	pDocument->getElementsByTagName (CComBSTR (L"gpx/trk"), & pTrkNodeList);

	CComPtr<IXMLDOMNodeList> pRteNodeList;
	pDocument->getElementsByTagName (CComBSTR (L"gpx/rte"), & pRteNodeList);

	if (pWptNodeList == NULL && pTrkNodeList == NULL && pRteNodeList == NULL) {
		ReportWarning ("XML file does not contain waypoints/tracks/routes.");
		return lsFailed;
	}

	m_bstrLat = L"lat";
	m_bstrLon = L"lon";

	//
	//
	//

	m_pMap = _pMap;

	m_file_refs.clear ();
	m_file_refs.push_back (file_ref_t ());
	m_file_refs.back ().strFileName = _strFileName;
	m_file_refs.back ().bAttachment = true;

	m_pMap->cHeaderLen = 0;

	SetProgressStatus (0);

	long lWaypoints = 0;
	pWptNodeList->get_length (& lWaypoints);

	long lTracks = 0;
	pTrkNodeList->get_length (& lTracks);

	long lRoutes = 0;
	pRteNodeList->get_length (& lRoutes);

	//
	// Enumerate waypoints.
	//
	for (long lWaypoint = 0; lWaypoint < lWaypoints; ++ lWaypoint) {
		CComPtr<IXMLDOMNode> pWptNode;
		pWptNodeList->get_item (lWaypoint, & pWptNode);

		point_t pt;
		GetPosition (pWptNode, pt);

		//
		// Create waypoint.
		//
		rgn_t & rgn = m_pMap->CreateRgn (rkPoint);
		rgn.SetWaypointType ();

		rgn.pFileRef = & m_file_refs.back ();
		rgn.cBeginPos = 0;
		rgn.cEndPos   = 0;
		rgn.cFileLine = -1;

		rgn.origins.push_back (rgn_origin_t ());
		rgn_origin_t & origin = rgn.origins.back ();
		origin.cLevel = 0;
		origin.point = pt;

		waypoint_info_t * const pWptExtra = rgn.pExtra->wtr.CreateWaypoint ();

		// Other attributes.
		CComPtr<IXMLDOMNodeList> pNodeList;
		pWptNode->get_childNodes (& pNodeList);
		long lNodes = 0;
		pNodeList->get_length (& lNodes);
		for (long lNode = 0; lNode < lNodes; ++ lNode) {
			CComPtr<IXMLDOMNode> pNode;
			pNodeList->get_item (lNode, & pNode);

			CComBSTR bstrNodeName;
			pNode->get_nodeName (& bstrNodeName);
			if (bstrNodeName.m_str == NULL)
				continue;

			if (::wcsicmp (bstrNodeName, L"name") == 0) {
				// Name.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				rgn.strLabel.assign (W2A (bstrText.m_str), bstrText.Length ());
			} else if (::wcsicmp (bstrNodeName, L"desc") == 0) {
				// Description.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				rgn.strComment.assign (W2A (bstrText.m_str), bstrText.Length ());
			} else if (::wcsicmp (bstrNodeName, L"ele") == 0) {
				// Elevation.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				float fAltitude = 0;
				const wchar_t * p = NULL;
				if (ParseReal (bstrText.m_str, bstrText.m_str + bstrText.Length (), & p, fAltitude)) {
					pWptExtra->wFlags |= waypoint_info_t::defAltitude;
					pWptExtra->fAltitude = fAltitude;
				} else
					ReportWarning ("Failed to read waypoint elevation.");
			} else if (::wcsicmp (bstrNodeName, L"time") == 0) {
				// Date and Time.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				DATE dt;
				bool bHasMilliseconds = false;
				if (_ParseDateTime (bstrText.m_str, bstrText.m_str + bstrText.Length (), dt, bHasMilliseconds)) {
					pWptExtra->wFlags |= waypoint_info_t::defTime;
					pWptExtra->dtTime = dt;
				} else
					ReportWarning ("Failed to read waypoint time.");
			} else if (::wcsicmp (bstrNodeName, L"url") == 0) {
				// URL.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				pWptExtra->strURL.assign (W2A (bstrText.m_str), bstrText.Length ());
			}
			// cmt
			// sym
			// type
		}

		SetProgressStatus (fast_floor (50*(.1f*(lWaypoint + 1)/(.1*lWaypoints + lTracks + lRoutes)) + .5f));
	}

	//
	// Enumerate tracks.
	//
	for (long lTrack = 0; lTrack < lTracks; ++ lTrack) {
		CComPtr<IXMLDOMNode> pTrkNode;
		pTrkNodeList->get_item (lTrack, & pTrkNode);

		//
		// Create track.
		//
		rgn_t & rgn = m_pMap->CreateRgn (rkPolyline);
		rgn.SetTrackType ();

		rgn.pFileRef = & m_file_refs.back ();
		rgn.cBeginPos = 0;
		rgn.cEndPos   = 0;
		rgn.cFileLine = -1;

		CComPtr<IXMLDOMNodeList> pNodeList;
		pTrkNode->get_childNodes (& pNodeList);

		track_info_t * const pTrkExtra = rgn.pExtra->wtr.CreateTrack ();

		size_t cElement = 0;

		const float fProgress0 = 50*((.1f*lWaypoints + lTrack    )/(.1*lWaypoints + lTracks + lRoutes));
		const float fProgress1 = 50*((.1f*lWaypoints + lTrack + 1)/(.1*lWaypoints + lTracks + lRoutes));

		long lNodes = 0;
		pNodeList->get_length (& lNodes);
		for (long lNode = 0; lNode < lNodes; ++ lNode) {
			CComPtr<IXMLDOMNode> pNode;
			pNodeList->get_item (lNode, & pNode);

			CComBSTR bstrNodeName;
			pNode->get_nodeName (& bstrNodeName);
			if (bstrNodeName.m_str == NULL)
				continue;

			if (::wcsicmp (bstrNodeName, L"trkseg") == 0) {
				// Create track's element.
				++ cElement;
				rgn.elements.push_back (rgn_element_t ());
				rgn_element_t & element = rgn.elements.back ();
				element.cFileOffset = -1;
				element.cLevel = 0;

				trackpoints_t & TrackPoints = element.pExtra->TrackPoints;

				CComPtr<IXMLDOMNodeList> pTrkPtNodeList;
				pNode->selectNodes (CComBSTR (L"./trkpt"), & pTrkPtNodeList);

				long lTrkPts = 0;
				pTrkPtNodeList->get_length (& lTrkPts);

				element.points.reserve (lTrkPts);
				TrackPoints.reserve (lTrkPts);

				for (long lTrkPt = 0; lTrkPt < lTrkPts; ++ lTrkPt) {
					::SetProgressStatus (fast_floor (fProgress0 + lTrkPt*(fProgress1 - fProgress0)/lTrkPts + .5f));

					CComPtr<IXMLDOMNode> pTrkPtNode;
					pTrkPtNodeList->get_item (lTrkPt, & pTrkPtNode);

					// Position.
					point_t pt;
					GetPosition (pTrkPtNode, pt);
					element.points.push_back (pt);

					// Other attributes.
					TrackPoints.push_back (trackpoint_info_t ());
					trackpoint_info_t & PointExtra = TrackPoints.back ();

					CComPtr<IXMLDOMNodeList> pNodeList;
					pTrkPtNode->get_childNodes (& pNodeList);
					long lNodes = 0;
					pNodeList->get_length (& lNodes);
					for (long lNode = 0; lNode < lNodes; ++ lNode) {
						CComPtr<IXMLDOMNode> pNode;
						pNodeList->get_item (lNode, & pNode);

						CComBSTR bstrNodeName;
						pNode->get_nodeName (& bstrNodeName);
						if (bstrNodeName.m_str == NULL)
							continue;

						if (::wcsicmp (bstrNodeName, L"ele") == 0) {
							// Elevation.
							CComBSTR bstrText;
							pNode->get_text (& bstrText);

							float fAltitude = 0;
							const wchar_t * p = NULL;
							if (ParseReal (bstrText.m_str, bstrText.m_str + bstrText.Length (), & p, fAltitude)) {
								PointExtra.wFlags |= trackpoint_info_t::defAltitude;
								PointExtra.fAltitude = fAltitude;
							} else
								ReportWarning ("Failed to read trackpoint elevation.");
						} else if (::wcsicmp (bstrNodeName, L"speed") == 0) {
							// Speed.
							CComBSTR bstrText;
							pNode->get_text (& bstrText);

							float fSpeedInMS = 0;
							const wchar_t * p = NULL;
							if (ParseReal (bstrText.m_str, bstrText.m_str + bstrText.Length (), & p, fSpeedInMS)) {
								PointExtra.wFlags |= trackpoint_info_t::defSpeed;
								PointExtra.fSpeed = 3.6*fSpeedInMS;
							} else
								ReportWarning ("Failed to read trackpoint speed.");
						} else if (::wcsicmp (bstrNodeName, L"pdop") == 0) {
							// DOP.
							CComBSTR bstrText;
							pNode->get_text (& bstrText);

							float fDOP = 0;
							const wchar_t * p = NULL;
							if (ParseReal (bstrText.m_str, bstrText.m_str + bstrText.Length (), & p, fDOP)) {
								PointExtra.wFlags |= trackpoint_info_t::defDOP;
								PointExtra.fDOP = fDOP;
							} else
								ReportWarning ("Failed to read trackpoint DOP.");
						} else if (::wcsicmp (bstrNodeName, L"time") == 0) {
							// Date and Time.
							CComBSTR bstrText;
							pNode->get_text (& bstrText);

							DATE dt;
							bool bHasMilliseconds = false;
							if (_ParseDateTime (bstrText.m_str, bstrText.m_str + bstrText.Length (), dt, bHasMilliseconds)) {
								PointExtra.wFlags |= trackpoint_info_t::defTime;
								if (bHasMilliseconds)
									PointExtra.wFlags |= trackpoint_info_t::defTimeMilliseconds;
								PointExtra.dtTime = dt;
							} else
								ReportWarning ("Failed to read trackpoint time.");
						}
					}
				}
			} else if (::wcsicmp (bstrNodeName, L"name") == 0) {
				// Name.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				rgn.strLabel.assign (W2A (bstrText.m_str), bstrText.Length ());
			} else if (::wcsicmp (bstrNodeName, L"desc") == 0) {
				// Description.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				rgn.strComment.assign (W2A (bstrText.m_str), bstrText.Length ());
			} else if (::wcsicmp (bstrNodeName, L"url") == 0) {
				// URL.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				pTrkExtra->strURL.assign (W2A (bstrText.m_str), bstrText.Length ());
			}
		}

		SetProgressStatus (fast_floor (fProgress1 + .5f));
	}

	//
	// Enumerate routes.
	//
	for (long lRoute = 0; lRoute < lRoutes; ++ lRoute) {
		CComPtr<IXMLDOMNode> pRteNode;
		pRteNodeList->get_item (lRoute, & pRteNode);

		//
		// Create route.
		//
		rgn_t & rgn = m_pMap->CreateRgn (rkPolyline);
		rgn.SetRouteType ();

		rgn.pFileRef  = & m_file_refs.back ();
		rgn.cBeginPos = 0;
		rgn.cEndPos   = 0;
		rgn.cFileLine = -1;

		rgn.elements.push_back (rgn_element_t ());
		rgn_element_t & element = rgn.elements.back ();
		element.cFileOffset = -1;
		element.cLevel = 0;

		route_info_t  * const pRteExtra = rgn.pExtra->wtr.CreateRoute ();
		routepoints_t & rpoints = rgn.pExtra->wtr.route ()->points;

		CComPtr<IXMLDOMNodeList> pNodeList;
		pRteNode->get_childNodes (& pNodeList);

		long lNodes = 0;
		pNodeList->get_length (& lNodes);

		element.points.reserve (lNodes);
		rpoints.reserve (lNodes);

		for (long lNode = 0; lNode < lNodes; ++ lNode) {
			CComPtr<IXMLDOMNode> pNode;
			pNodeList->get_item (lNode, & pNode);

			CComBSTR bstrNodeName;
			pNode->get_nodeName (& bstrNodeName);
			if (bstrNodeName.m_str == NULL)
				continue;

			if (::wcsicmp (bstrNodeName, L"rtept") == 0) {
				// Position.
				point_t pt;
				GetPosition (pNode, pt);
				element.points.push_back (pt);

				//
				// Create waypoint.
				//
				rgn_t & wpt = m_pMap->CreateRgn (rkPoint);
				wpt.SetWaypointType ();

				wpt.pFileRef = & m_file_refs.back ();
				wpt.cBeginPos = 0;
				wpt.cEndPos   = 0;
				wpt.cFileLine = -1;

				wpt.origins.push_back (rgn_origin_t ());
				rgn_origin_t & origin = wpt.origins.back ();
				origin.cLevel = 0;
				origin.point = pt;

				waypoint_info_t * const pWptExtra = wpt.pExtra->wtr.CreateWaypoint ();

				// Other attributes.
				rpoints.push_back (routepoint_info_t ());
				routepoint_info_t & PointExtra = rpoints.back ();
				PointExtra.pWaypoint = & wpt;

				CComPtr<IXMLDOMNodeList> pNodeList;
				pNode->get_childNodes (& pNodeList);
				long lNodes = 0;
				pNodeList->get_length (& lNodes);
				for (long lNode = 0; lNode < lNodes; ++ lNode) {
					CComPtr<IXMLDOMNode> pNode;
					pNodeList->get_item (lNode, & pNode);

					CComBSTR bstrNodeName;
					pNode->get_nodeName (& bstrNodeName);
					if (bstrNodeName.m_str == NULL)
						continue;

					if (::wcsicmp (bstrNodeName, L"ele") == 0) {
						// Waypoint elevation.
						CComBSTR bstrText;
						pNode->get_text (& bstrText);

						float fAltitude = 0;
						const wchar_t * p = NULL;
						if (ParseReal (bstrText.m_str, bstrText.m_str + bstrText.Length (), & p, fAltitude)) {
							pWptExtra->wFlags |= waypoint_info_t::defAltitude;
							pWptExtra->fAltitude = fAltitude;
						} else
							ReportWarning ("Failed to read routepoint elevation.");
					} else if (::wcsicmp (bstrNodeName, L"time") == 0) {
						// Waypoint date and time.
						CComBSTR bstrText;
						pNode->get_text (& bstrText);

						DATE dt;
						bool bHasMilliseconds = false;
						if (_ParseDateTime (bstrText.m_str, bstrText.m_str + bstrText.Length (), dt, bHasMilliseconds)) {
							pWptExtra->wFlags |= waypoint_info_t::defTime;
							pWptExtra->dtTime = dt;
						} else
							ReportWarning ("Failed to read routepoint time.");
					} else if (::wcsicmp (bstrNodeName, L"name") == 0) {
						// Waypoint name.
						CComBSTR bstrText;
						pNode->get_text (& bstrText);

						wpt.strLabel.assign (W2A (bstrText.m_str), bstrText.Length ());
					} else if (::wcsicmp (bstrNodeName, L"desc") == 0) {
						// Waypoint description.
						CComBSTR bstrText;
						pNode->get_text (& bstrText);

						wpt.strComment.assign (W2A (bstrText.m_str), bstrText.Length ());
					} else if (::wcsicmp (bstrNodeName, L"url") == 0) {
						// Waypoint URL.
						CComBSTR bstrText;
						pNode->get_text (& bstrText);

						pWptExtra->strURL.assign (W2A (bstrText.m_str), bstrText.Length ());
					}
				}
			} else if (::wcsicmp (bstrNodeName, L"name") == 0) {
				// Route name.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				rgn.strLabel.assign (W2A (bstrText.m_str), bstrText.Length ());
			} else if (::wcsicmp (bstrNodeName, L"desc") == 0) {
				// Route description.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				rgn.strComment.assign (W2A (bstrText.m_str), bstrText.Length ());
			} else if (::wcsicmp (bstrNodeName, L"url") == 0) {
				// Route URL.
				CComBSTR bstrText;
				pNode->get_text (& bstrText);

				pRteExtra->strURL.assign (W2A (bstrText.m_str), bstrText.Length ());
			}
		}

		SetProgressStatus (fast_floor (50*((.1f*lWaypoints + lTracks + lRoute + 1)/(.1*lWaypoints + lTracks + lRoutes)) + .5f));
	}

	SetProgressStatus (50);

	m_pMap->file_refs.swap (m_file_refs);

	// Suppress warning about empty Zoom etc.
	m_pMap->bNoHeader = true;

	return lsOK;
}
